package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**聊天室服务器端 版本3
 * 目前先实现将客户端发送过来的消息回复给该客户端
 * 具体做法：
 *
 *
 *
 *
 * */

public class Server5 {
    /**ServerSocket运行在服务端，作用有两个
     * 1、向系统申请服务端口，客户端的socket就是通过这个端口与服务端建立连接的
     * 2、监听服务端口，一旦一个客户端通过该端口建立连接，则会自动创建一个socket,并通过该socket与该客户端进行交互
     */
    private ServerSocket serverScokt;
    //v4:该集合用于保存所有客户端的输出流，用于广播消息
    private List<PrintWriter> allOut =new ArrayList<>();
    public Server5(){
        try {
            System.out.println("正在启动服务器");
            serverScokt=new ServerSocket(8089);//把socket比作电话，ServerSocket相当于某客服中心的总机
            System.out.println("服务器启动完毕");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void start(){
        /*
        ServerSocket提供的重要方法：
        Socket accept()
        用于接收客户端的连接，并返回一个Socket实例与连接的客户端交互
        该方法是一个阻塞方法，调用后程序会“卡住”，直到一个客户端连接为止
         */
        try {
            while (true) {
                System.out.println("等待客户端连接");
                Socket socket = serverScokt.accept();//接收客户端请求并返回一个socket实例与连接的客户端交互
                System.out.println("一个客户端连接上了");
                //启动一个线程负责与该客户端交互
                ClientHandler handler=new ClientHandler(socket);//线程任务
                Thread t=new Thread(handler);
                t.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server5 server=new Server5();
        server.start();
    }
    private class ClientHandler implements Runnable{//线程任务
        private Socket socket;//通过构造方法与属性值传递run方法需要的参数
        private String host;//记录客户端的ip地址


        public ClientHandler(Socket socket){
            this.socket=socket;
            this.host=socket.getInetAddress().getHostAddress();
        }
        public void run(){
            PrintWriter pw=null;

            try {
                InputStream in=socket.getInputStream();//读取远端计算机发送来的数据
                InputStreamReader isr=new InputStreamReader(in, StandardCharsets.UTF_8);//字符转换流
                BufferedReader br=new BufferedReader(isr);

                //V3：通过socket获取输出流用于给客户端发消息
                OutputStream out=socket.getOutputStream();
                OutputStreamWriter osw=new OutputStreamWriter(out,StandardCharsets.UTF_8);
                BufferedWriter bw=new BufferedWriter(osw);
                 pw=new PrintWriter(bw,true);

                //V4：将对应该客户端的输出流存入到共享集合allOut中，便于其他线程可以访问到
                synchronized (allOut){
                    allOut.add(pw);
                }

                //v5:广播消息，该客户端上线了
                sendMessage(host+"上线了，当前在线人数："+allOut.size());

                String message;
                while ((message=br.readLine())!=null){
                    //V5:读取到客户端消息广播改为调用
                  sendMessage(host+"说："+message);
                }
            } catch (IOException e) {

            }finally{
                //删除集合中的元素
                synchronized (allOut){
                    allOut.remove(pw);
                }

                sendMessage(host+"下线了，当前在线人数："+allOut.size());
                try {
                    //关闭socket,释放资源
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }

        }
    }

    /**
     * V5的功能，实现广播消息的方法
     * 将消息发给所有客户端
     * @param message
     */
    private void sendMessage(String message){
        System.out.println(message);
        //v4:遍历allOut集合，给所有客户端发送这个消息
        //v5:新循环就是迭代器，迭代器要求遍历过程中不能用集合方法增删元素，所以需要和上面的增删互斥
        synchronized (allOut){
            for (PrintWriter o:allOut){
                o.println(message);
            }

        }


    }
}
